feat(feed): sponsored strip docked on the main feed [mock-up] - #6490
Draft
tsahimatsliah wants to merge 22 commits into
Draft
feat(feed): sponsored strip docked on the main feed [mock-up]#6490tsahimatsliah wants to merge 22 commits into
tsahimatsliah wants to merge 22 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
A bottom rail carrying one lead sponsor and a wall of partner marks. The lead mark keeps brand colour, sits ~20% larger than the wall, and is the only link and the only hover in the strip. NVIDIA's lockup renders inline rather than as a flat file so its wordmark can take currentColor — black on the light feed, white on the dark one — while the symbol holds #76B900. A single flat asset cannot do that. The wall is silhouetted through a CSS mask so one asset works in both themes, sized by area rather than cap height so a 2:1 mark and a 6:1 lockup carry the same weight, reshuffled per page load so no advertiser is permanently first, and trimmed to the marks that fit whole rather than clipped behind a fade. Sponsors are fixtures. Real inventory needs an ad-service source.
The dock stacks a value rail beneath the sponsor row, which does two jobs at once. It gives the strip a reason to exist for the reader: a permanent bar carrying only advertising is rent, one carrying something the reader came for is a feature that happens to be sponsored. It also answers the browser's link tooltip. That tooltip paints over the bottom-left corner, cannot be read or suppressed from a page, and is as wide as the URL inside it — measured on the live feed, post slugs run 393px and promoted cards' signed click-throughs run to Chrome's 50%-of-viewport cap. Horizontal clearance therefore cannot be a guarantee; vertical clearance is 26px whatever the URL says. The rail gives the paid row 32px of it, and each rail opens with its label on the left, where the tooltip lands first, so what it covers is a word rather than a number. Eleven channels, all backed by data already in the app — trendingTags, userStreak, the leaderboard queries, live rooms, opportunities, poll posts. The label doubles as the switcher, so the control costs no space the row was not already spending. The choice lives in localStorage and is read after mount, so server and client agree on the first paint.
MOCK-UP — not for merge. Wired unconditionally so the design can be reviewed on a preview deployment rather than only in Storybook. The dock is sticky rather than fixed: fixed positions against the viewport, so it spans the whole window and slides under the left sidebar. Sticky keeps it in flow inside the layout's padded main, which is where the sidebar offset already lives, so it spans the feed and follows that offset across both layout variants without knowing what either is. Missing before this could ship: an ad-service source instead of the fixture, a feature flag, impression logging, and the rail wired to its real queries.
Stories for the shipped design and for the exploration behind it: five placements evaluated over a real feed, the dock and its eleven rails, ten ways to hold a bar at the bottom, and ten technical answers to the link tooltip. The rejected explorations live in the storybook package rather than in shared. They are decision-making artifacts, not product code, and nothing outside their own stories imports them. The tooltip bench is a testbed rather than a gallery: hovering a card draws a stand-in at the real size and corner, and the feed's link length is switchable, because the card href comes from the post slug and benching against a short one hides the problem entirely. Storybook's tailwind config now scans packages/extension. Utilities used only there generated no CSS, so ShortcutLinks and any story importing an extension component rendered a layout the app would not. Mockup-to-eng-pass: 1
The breadcrumbs started flush against the site header — 0px above them — and then sat 16px clear of the tab strip, so the two read as one loose block instead of a heading with its tabs under it. 16px above the breadcrumbs, 8px between them and the tabs. The 16px from the tabs down to the cards is unchanged. Cherry-picked from #6516. Byte-identical, so that PR merging is a no-op here. Mockup-to-eng-pass: 1
The previous commits added a gutter on FeedContainer while FeedPage was already applying `laptop:p-10` in the legacy layout. Both were in the same ancestry, so they stacked — and because `isV2` resolves after mount, which of them applied changed as the flag settled. The feed visibly jumped between a 64px inset and a 24px one depending on whether you loaded the page, refreshed it, or navigated to it. FeedPage now sets the inset for both variants and is the only place that does: `feedGutterV2` under v2, the existing `pageMainClassNames` otherwise. v2 previously set nothing at all, which is what left the cards flush against the frame. The breadcrumbs and tab strip sit outside FeedPage, so they take the same value through `useFeedGutter` rather than a hardcoded copy, and it is in the memo's deps so it cannot go stale. Cherry-picked from #6516. Byte-identical, so that PR merging is a no-op here. Mockup-to-eng-pass: 1
`isV2` is not stable across client-side navigation: it reads true on load and false after a route change. Any inset keyed to it therefore changed as you moved around the app — the feed sat 24px in on arrival and 40px in after clicking a tab, and refreshing "fixed" it by landing on the other state. The feed now keeps one inset, `pageMainClassNames`, whatever the layout experiment is doing. v2's special case is gone: it set nothing at all, which is what left the cards flush against the frame. The breadcrumbs and tab strip take the horizontal half of the same value through the `feedGutter` constant. Cherry-picked from #6516. Byte-identical, so that PR merging is a no-op here. Mockup-to-eng-pass: 1
…share The feed renders through two different page containers depending on layout and route: FeedPage, which carries `pageMainClassNames`, and FeedPageLayoutList, which forces `!px-0`. Which one you get flips with `isV2` — itself unstable across client-side navigation — so an inset placed on either covered some routes and not others, and stacked with the other where both applied. That is the whole bug: 64px on one arrival, 24px on another, 0px after a route change. FeedContainer is the only element common to both, so the inset lives there now and nowhere else. FeedPage keeps its vertical padding and gives up its horizontal, which it could not apply everywhere anyway. One value, `px-4 tablet:px-6 laptop:px-10`, for every feed route and both layout variants — 40px at laptop, matching what the legacy path already gave. The breadcrumbs and tab strip share the constant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit a366dd8) Cherry-picked from #6516. Byte-identical, so that PR merging is a no-op here. Mockup-to-eng-pass: 1
Explore showed a large gap above the breadcrumbs on arrival that a reload "fixed" — the same shape of bug as the horizontal inset, in the other axis. `disableTopPadding` was keyed to `shouldUseListFeedLayout`, and that value is not stable: `enableSsrSafeLayout` forces the list layout before mount and hands over to the real one after, so the page's 40px top padding was present in one state and zeroed in the other. The header sat 104px down or 64px down depending on how you arrived. Explore now keeps that padding in both layouts. It is the one feed that renders a breadcrumb and tab header above the cards, and with the padding zeroed that header is jammed against the site header. The `pt-4` added earlier to the Explore header goes away with it. It was compensating for this missing padding, which is why the gap read as too large in the state where both applied. Production has no such compensation, and Explore now matches it: 104px to the breadcrumbs, 64px of header offset plus the page's 40px. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Cherry-picked from #6516. Byte-identical, so that PR merging is a no-op here. Mockup-to-eng-pass: 1
CodeRabbit takes the paid slot and NVIDIA becomes a regular partner mark, silhouetted with the rest of the wall. CodeRabbit has the same problem NVIDIA did: an orange mark with a near-black (#171717) wordmark, which is the only version the advertiser library ships and which disappears on the dark feed. So it renders inline as well — the mark keeps #FF570A, the wordmark takes `currentColor` and flips black to white with the theme. The split is by fill colour rather than by eye: ten #171717 paths are the wordmark, #FF570A is the mark, #FEFEFE is the detail inside it. NVIDIA is not in the advertiser library, so its silhouette source is a data URI rather than a hosted file — the app and Storybook would otherwise each need their own copy, and only one of them had it last time. Verified in both themes: wordmark computes to white on dark and #0f1218 on light, mark holds #FF570A in both. Mockup-to-eng-pass: 1
Leaves twelve marks: eleven from the advertiser library plus NVIDIA. Mockup-to-eng-pass: 1
"Hot right now" becomes "Breaking news", in the rail, the switcher menu and the feed wiring's default. The label also sat 4px right of "Made possible by" above it: the dropdown trigger carries `px-1` so its hover chip has something to sit on, and that padding pushed the text in. `-ml-1` cancels it, so the two rows start on the same left edge and the chip keeps its padding. Mockup-to-eng-pass: 1
…the wall NVIDIA returns to the paid slot with its two-tone lockup: symbol on #76B900, wordmark inheriting `currentColor` so it flips with the theme. CodeRabbit becomes a regular silhouetted mark, taken from the advertiser library like the rest of the wall. The data URI that let NVIDIA be masked in the wall goes with it — it was only needed because NVIDIA has no library asset, and the lead slot renders inline instead. Both lockups stay: the one not currently leading appears in LogoTreatment's candidate list, which is where the two-tone argument is made, so neither is dead code. Wall stays at twelve. Mockup-to-eng-pass: 1
CodeRabbit's mark carries a white detail painted inside the orange shape. The wall silhouettes through a CSS mask, which reads paint as opaque, so the flat asset came through as a featureless disc rather than the rabbit. The lockup now renders inline in the wall as well, and in that treatment the mark and its detail become one path under `evenodd`, so the detail is a hole. Colour treatment is unchanged. `Sponsor.Artwork` is therefore used in both treatments and is told which one it is drawing, via a shared `LockupProps`. NVIDIA's lockup takes the same prop: its symbol keeps #76B900 in colour and joins the silhouette in the wall, so either sponsor can hold either slot without a second asset. This is the general fix for the knockout problem the LogoTreatment story documents — it just needs an inline lockup rather than a flat file, which is what the two sponsors that hit it already have. Mockup-to-eng-pass: 1
…ped hole The faithful monochrome transcription of the mark keeps the brand disc and knocks the rabbit out of it. That is correct and unreadable: at the 16-20px the partner wall gives a logo, the disc is the ink and the rabbit is a smudge of background, so the mark reads as a plain circle next to eleven other light-on-dark glyphs. Drop the disc in the silhouette treatment and draw the rabbit itself. The colour treatment, which has the orange to carry the disc, is unchanged. Mockup-to-eng-pass: 1
…bbit-shaped hole" This reverts commit fc8a5cd. The wall shows the knockout — disc plus rabbit-shaped hole — which is CodeRabbit's actual mark. Rabbit-only reads better at 16-20px but is artwork we invented for them, and this is a paid slot. Noted in the file so it does not get "fixed" again. Mockup-to-eng-pass: 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A sponsored strip docked to the bottom of the main feed. Sponsors and every rail's data are hard-coded fixtures. Nothing here has been sold.
What it is
A 72px dock, flush and sticky, spanning the feed column:
The lead mark keeps brand colour, sits ~20% larger than the wall, and is the only link and the only hover in the strip. NVIDIA's lockup renders inline rather than as a flat file so its wordmark can take
currentColor— black on the light feed, white on the dark one — while the symbol holds#76B900. A single flat asset cannot do that.The partner wall is silhouetted through a CSS mask so one asset works in both themes, sized by area rather than cap height so a 2:1 mark and a 6:1 lockup carry the same weight, reshuffled on every page load so no advertiser is permanently first, and trimmed to the marks that fit whole — never a half logo under a fade.
The value rail defaults to Hot right now. Eleven channels, all backed by data already in the app:
trendingTags,userStreak, the leaderboard queries, live rooms, opportunities, poll posts. The choice is kept inlocalStorage.Why there are two rows
The browser's link tooltip paints over the bottom-left corner, cannot be read or suppressed from a page, and is exactly as wide as the URL inside it. Measured on the live feed, 28 links on one screen:
Horizontal clearance therefore cannot be a guarantee — it would need half the bar. Vertical clearance is 26px whatever the URL says. The rail gives the paid row 32px of it, and each rail opens with its label on the left, where the tooltip lands first, so what it covers is a word rather than a number.
Worth fixing separately: promoted cards link through a signed token, so that URL carries a whole JWT.
Storybook
Rejected explorations live in the storybook package, not in
shared— they are decision-making artifacts, not product code.Before this could ship
business.daily.dev.svg, which mask to a solid block🤖 Generated with Claude Code
Preview domain
https://feat-feed-sponsored-strip.preview.app.daily.dev